﻿///<copyright>
/// Copyright © 2009 Stephen Styrchak. All Rights Reserved.
///</copyright>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Ferpect.ComponentModel.Design;
using Ferpect.ComponentModel.Sprites;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
#if WINDOWS
using System.Drawing.Design;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
#endif

namespace Ferpect.ComponentModel
{
#if WINDOWS
    [ToolboxItemFilter("Ferpect.ComponentModel.Screen", ToolboxItemFilterType.Allow)]
    [DesignerSerializer(Serializers.ScreenTypeSerializer, typeof(TypeCodeDomSerializer))]
    [DesignerSerializer(Serializers.ScreenControlSerializer, typeof(CodeDomSerializer))]
    [Designer(Designers.ScreenComponentDesigner, typeof(IDesigner))]
#endif
    [ViewDocumentDesigner(Designers.ScreenDesigner)]
    [DefaultEvent("FocusChanged")]
    public class Screen : ViewComponent, IScreen
    {
        private IGameLoop gameState;
        private IScreenManager screenManager;
        private IViewportService viewportService;
        private Rectangle viewportArea;
        private Rectangle titleSafeArea;
        private AmbientProperties ambientProperties;
        private ScreenContext context;
        private GameComponentCollection gameComponents;
        private Container container;

        private List<Sprite> sprites;
        private SpriteBatchInfo spriteBatchInfo;
        private SpriteBatchStack spriteBatchStack;

        // IScreen state
        private EventHandler<EventArgs> backColorChangedHandlers;
        private EventHandler<EventArgs> focusChangedHandlers;
        private Nullable<Color> backColor;
        private bool hasFocus;
        private string name;

        public Screen()
        {
            this.Enabled = false;
            this.Visible = false;
            this.sprites = new List<Sprite>();
            this.gameComponents = new GameComponentCollection();
            this.gameComponents.ComponentAdded += this.OnGameComponentAdded;
            this.gameComponents.ComponentRemoved -= this.OnGameComponentRemoved;
        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public IGameLoop GameState { get { return this.gameState; } }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public IScreenManager ScreenManager { get { return this.screenManager; } }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public AmbientProperties AmbientProperties { get { return this.ambientProperties; } }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public ScreenContext ScreenContext { get { return this.context; } }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public List<Sprite> Sprites
        {
            get { return this.sprites; }
        }

        protected virtual void DrawBackground()
        {
            if (this.BackColor.HasValue && this.Initialized)
            {
                this.GraphicsDevice.Clear(this.BackColor.Value);
            }
        }

        protected void DrawSprites()
        {
            if (this.spriteBatchStack != null && this.sprites.Count > 0)
            {
                int countBefore = this.spriteBatchStack.Count;

                this.spriteBatchStack.Push(this.SpriteBatchInfo);
                foreach (Sprite sprite in this.sprites)
                {
                    sprite.Draw(this.spriteBatchStack);
                }
                this.spriteBatchStack.Pop();

                if (this.spriteBatchStack.Count != countBefore)
                {
                    throw new InvalidOperationException("Mismatched calls to Push and Pop in SpriteBatchStack during DrawSprites.");
                }
            }
        }

        protected virtual void Draw()
        {
        }

        protected virtual SpriteBatchInfo SpriteBatchInfo
        {
            get
            {
                if (this.spriteBatchInfo == null)
                {
                    this.spriteBatchInfo = new SpriteBatchInfo(this.GraphicsDevice);
                }
                return this.spriteBatchInfo;
            }
        }

        protected override void LoadContent()
        {
            this.container = new Container(this.gameState.Services);
            foreach (Sprite sprite in this.sprites)
            {
                if (sprite.Site == null)
                {
                    this.container.Add(sprite);
                }
                sprite.LoadContent(this.Content);
            }
            base.LoadContent();
        }

        protected override void UnloadContent()
        {
            foreach (Sprite sprite in this.sprites)
            {
                sprite.UnloadContent();
            }
            base.UnloadContent();
        }

        #region IDrawable members

        public override void Draw(GameTime gameTime)
        {
            this.DrawBackground();
            this.Draw();
            this.DrawSprites();
        }

        #endregion

        #region IGameComponent members

        public override void Initialize()
        {
            if (!this.Initialized)
            {
                ISite site = this.Site;

                if (site == null)
                {
                    throw new InvalidOperationException("IComponent.Site is null. A site must be provided before calling IGameComponent.Initialize().");
                }

                this.gameState = (IGameLoop)site.GetService(typeof(IGameLoop));
                this.screenManager = (IScreenManager)site.GetService(typeof(IScreenManager));
                this.ambientProperties = (AmbientProperties)site.GetService(typeof(AmbientProperties));
                this.viewportService = (IViewportService)site.GetService(typeof(IViewportService));

                if (this.Content == null)
                {
                    this.Content = this.gameState.Content;
                }

                this.spriteBatchStack = new SpriteBatchStack();
                this.Container.Add(this.spriteBatchStack);

                foreach (IGameComponent component in this.Components)
                {
                    this.GameState.AddComponent(component);
                }
                this.SetAnchorPositions();
            }

            base.Initialize();
        }

        #endregion

        #region IUpdateable

        public override void Update(GameTime gameTime)
        {
            if (this.viewportArea != this.viewportService.ViewportArea || this.titleSafeArea != this.viewportService.TitleSafeArea)
            {
                this.SetAnchorPositions();
            }
        }

        private void SetAnchorPositions()
        {
            this.viewportArea = this.viewportService.ViewportArea;
            this.titleSafeArea = this.viewportService.ViewportArea;
            foreach (Sprite sprite in this.sprites)
            {
                sprite.SetAnchorPosition(viewportService);
            }
        }

        #endregion

        #region IScreen Members

        [Category("Property Changed")]
        public event EventHandler<EventArgs> FocusChanged
        {
            add
            {
                this.focusChangedHandlers += value;
            }
            remove
            {
                this.focusChangedHandlers -= value;
            }
        }

        [Category("Property Changed")]
        public event EventHandler<EventArgs> BackColorChanged
        {
            add
            {
                this.backColorChangedHandlers += value;
            }
            remove
            {
                this.backColorChangedHandlers -= value;
            }
        }

        [DefaultValue(null)]
        [Editor(UITypeEditors.ColorEditor, UITypeEditors.UITypeEditor)]
        [Category("Appearance")]
        [Description("When set, the screen will clear the graphics device to this color at the beginning of each Draw call.")]
        public Nullable<Color> BackColor
        {
            get
            {
                return this.backColor;
            }
            set
            {
                if (this.backColor != value)
                {
                    this.backColor = value;
                    if (this.backColorChangedHandlers != null)
                    {
                        this.backColorChangedHandlers(this, EventArgs.Empty);
                    }
                }
            }
        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool HasFocus
        {
            get
            {
                return this.hasFocus;
            }
            set
            {
                if (this.hasFocus != value)
                {
                    this.hasFocus = value;
                    if (this.focusChangedHandlers != null)
                    {
                        this.focusChangedHandlers(this, EventArgs.Empty);
                    }
                }
            }
        }

        [Browsable(false)]
        public string Name
        {
            get
            {
                if (this.name == null && this.Site != null)
                {
                    return this.Site.Name;
                }
                return this.name;
            }
            set
            {
                this.name = value;
            }
        }

        public virtual void Activate(ScreenContext context)
        {
            this.context = context;

            this.SetFocus(true);
            this.DrawOrder = context.RecommendedDrawOrder;
            this.Show();
        }

        public virtual void SetFocus(bool hasFocus)
        {
            this.HasFocus = hasFocus;
        }

        public virtual void Exit()
        {
            this.Hide();
        }

        public virtual void Hide()
        {
            this.HasFocus = false;
            this.Enabled = false;
            this.Visible = false;
        }

        public virtual void Show()
        {
            this.Enabled = true;
            this.Visible = true;
        }

        #endregion

        #region IUpdatableParentComponent

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public GameComponentCollection Components
        {
            get { return this.gameComponents; }
        }

        #endregion

        private void OnGameComponentAdded(object sender, GameComponentCollectionEventArgs args)
        {
            if (args != null)
            {
                IUpdatableChildComponent component = args.GameComponent as IUpdatableChildComponent;
                if (component != null)
                {
                    component.Parent = this;
                    if (this.Initialized)
                    {
                        this.GameState.AddComponent((IGameComponent)component);
                    }
                }
            }
        }

        private void OnGameComponentRemoved(object sender, GameComponentCollectionEventArgs args)
        {
            if (args != null)
            {
                IUpdatableChildComponent component = args.GameComponent as IUpdatableChildComponent;
                if (component != null)
                {
                    component.Parent = null;
                    if (this.Initialized)
                    {
                        this.GameState.RemoveComponent((IGameComponent)component);
                    }
                }
            }
        }
    }
}
